using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;

namespace PrisonStep
{
    /// <summary>
    /// This class describes our player in the game. 
    /// </summary>
    public class Player
    {
        #region Fields

        private enum States { Start, StanceStart, Stance, WalkStart, WalkLoopStart, WalkLoop, TurnStart, LeftTurn, RightTurn, Crouch, Crouching, StartRaiseBazooka, RaisingBazooka, EndRaiseBazooka, Aiming, LowerBazooka }

        /// <summary>
        /// Game that uses this player
        /// </summary>
        private PrisonGame game;

        //
        // Player location information.  We keep a x/z location (y stays zero)
        // and an orientation (which way we are looking).
        //

        /// <summary>
        /// Player location in the prison. Only x/z are important. y still stay zero
        /// unless we add some flying or jumping behavior later on.
        /// </summary>
        private Vector3 location = new Vector3(0, 0, 600); //new Vector3(275, 0, 1053);

        /// <summary>
        /// The player orientation as a simple angle
        /// </summary>
        private float orientation = 3.14f; // 1.6f;

        /// <summary>
        /// The player transformation matrix. Places the player where they need to be.
        /// </summary>
        private Matrix transform;

        /// <summary>
        /// The rotation rate in radians per second when player is rotating
        /// </summary>
        private float mPanRate = 2;

        /// <summary>
        /// The rate at which victoria will rotate when aiming.
        /// </summary>
        private float mPitchRate = 2;

        /// <summary>
        /// The current rotation that victoria's spine is at.
        /// This is rotated around the X-Axis to be implemented.
        /// </summary>
        private float mAzimuth = 0;

        /// <summary>
        /// The current rotation that victoria's spine is at.
        /// This is rotated around the Z-Axis to be implemented.
        /// </summary>
        private float mElevation = 0;

        // The section we start in
        private string LastRegion = "R_Section1";


        /// <summary>
        /// Our bazooka model
        /// </summary>
        private Bazooka mBazooka = null;
        /// <summary>
        /// Our player model
        /// </summary>
        private AnimatedModel mVictoria = null;

        /// <summary>
        /// Our players current state
        /// </summary>
        private States mState = States.Start;

        /// <summary>
        /// Are we currently moving forward?
        /// </summary>
        private bool mForward = false;

        /// <summary>
        /// The bone index for Victoria's hand
        /// </summary>
        private int mHandBone;



        /// <summary>
        /// Where the bazooka is relative to the world
        /// </summary>
        Matrix mBazookaMatrix;

        /// <summary>
        /// Whether or not out character is aiming the bazooka
        /// </summary>
        private bool mAiming = false;

        private float mCameraDistance = 0.0f;

        private KeyboardState mLastKeyBoardState;

        /// <summary>
        /// This dictionary captures the name and the list of
        /// the triangles for a region.
        /// </summary>
        private Dictionary<string, List<Vector2>> regions = new Dictionary<string, List<Vector2>>();

        private BoundingCylinder mBoundingCyl;
        #endregion

        #region Properties

        public Vector3 VictoriaLocation { get { return location; } }

        public Matrix BazMat { get { return mBazookaMatrix; } }

        public string Region { get { return LastRegion; } }

        public Bazooka BazookaProp { get { return mBazooka; } }

        public BoundingCylinder BoundingCyl { get { ComputeBoundingCylinder(); return mBoundingCyl; } }
        #endregion


        public Player(PrisonGame game)
        {
            this.game = game;
            SetPlayerTransform();

            mCameraDistance = new Vector3(-transform.Backward.X * 300, 200, -transform.Backward.Z * 300).Length();

            mVictoria = new AnimatedModel(game, "Victoria");
            mVictoria.AddAssetClip("dance", "Victoria-dance");
            mVictoria.AddAssetClip("stance", "Victoria-stance");
            mVictoria.AddAssetClip("walk", "Victoria-walk");
            mVictoria.AddAssetClip("walkstart", "Victoria-walkstart");
            mVictoria.AddAssetClip("walkloop", "Victoria-walkloop");
            mVictoria.AddAssetClip("rightturn", "Victoria-rightturn");
            mVictoria.AddAssetClip("leftturn", "Victoria-leftturn");
            mVictoria.AddAssetClip("lowerbazooka", "Victoria-lowerbazooka");
            mVictoria.AddAssetClip("raisebazooka", "Victoria-raisebazooka");
            mVictoria.AddAssetClip("crouchbazooka", "Victoria-crouchbazooka");
            mVictoria.AddAssetClip("walkloopbazooka", "Victoria-walkloopbazooka");
            mVictoria.AddAssetClip("walkstartbazooka", "Victoria-walkstartbazooka");
            mVictoria.Skinned = true;

            mBoundingCyl = new BoundingCylinder(location, 200, 30);

            mBazooka = new Bazooka(this.game);
        }

        /// <summary>
        /// Set the value of transform to match the current location
        /// and orientation.
        /// </summary>
        private void SetPlayerTransform()
        {
            transform = Matrix.CreateRotationY(orientation);
            transform.Translation = location;
            if (mVictoria != null)
            {
                Matrix lTorsoTransform = mVictoria.GetBoneTransforms("Bip01 Spine1");
                if (mAzimuth >= -1.0f || mAzimuth <= 1.0f)
                    lTorsoTransform *= Matrix.CreateRotationX(mAzimuth);

                lTorsoTransform *= Matrix.CreateRotationZ(mElevation);

                mVictoria.SetBoneTransforms("Bip01 Spine1", lTorsoTransform);

            }

        }


        public void LoadContent(ContentManager content)
        {
            mVictoria.LoadContent(content);
            mHandBone = mVictoria.model.Bones["Bip01 R Hand"].Index;
            mBazooka.LoadContent(content);
            AnimationPlayer lPlayer = mVictoria.PlayClip("lowerbazooka");

            Model model = content.Load<Model>("AntonPhibesCollision");
            // Nothing for now...

            Matrix[] M = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(M);

            foreach (ModelMesh mesh in model.Meshes)
            {
                // For accumulating the triangles for this mesh
                List<Vector2> triangles = new List<Vector2>();

                // Loop over the mesh parts
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {

                    // 
                    // Obtain the vertices for the mesh part
                    //

                    int numVertices = meshPart.VertexBuffer.VertexCount;
                    VertexPositionColorTexture[] verticesRaw = new VertexPositionColorTexture[numVertices];
                    meshPart.VertexBuffer.GetData<VertexPositionColorTexture>(verticesRaw);

                    //
                    // Obtain the indices for the mesh part
                    //

                    int numIndices = meshPart.IndexBuffer.IndexCount;
                    short[] indices = new short[numIndices];
                    meshPart.IndexBuffer.GetData<short>(indices);


                    //
                    // Build the list of triangles
                    //

                    for (int i = 0; i < meshPart.PrimitiveCount * 3; i++)
                    {
                        // The actual index is relative to a supplied start position
                        int index = i + meshPart.StartIndex;

                        // Transform the vertex into world coordinates
                        Vector3 v = Vector3.Transform(verticesRaw[indices[index] + meshPart.VertexOffset].Position, M[mesh.ParentBone.Index]);
                        triangles.Add(new Vector2(v.X, v.Z));
                    }
                }

                regions[mesh.Name] = triangles;
            }
        }

        public void Update(GameTime gameTime)
        {
            double lDeltaTotal = gameTime.ElapsedGameTime.TotalSeconds;

            do
            {
                KeyboardState lKeyboardState = Keyboard.GetState();
                double lDelta = lDeltaTotal;
                bool lLeft = false;
                bool lRight = false;
                KeyboardState keyboardState = Keyboard.GetState();

                if (keyboardState.IsKeyDown(Keys.Left))
                {
                    lLeft = true;
                }

                if (keyboardState.IsKeyDown(Keys.Right))
                {
                    lRight = true;
                }

                if (lKeyboardState.IsKeyDown(Keys.RightControl) && mLastKeyBoardState.IsKeyUp(Keys.RightControl))
                {
                    mAiming = !mAiming;
                }

                else 

                if (keyboardState.IsKeyDown(Keys.Up))
                    mForward = true;
                
                else if (keyboardState.IsKeyUp(Keys.Up))
                    mForward = false;

                //
                // State machine will go here
                //
                switch (mState)
                {
                    case States.Start:
                        mState = States.StanceStart;
                        lDelta = 0;
                        break;

                    case States.StanceStart:
                        mVictoria.PlayClip("raisebazooka").Speed = 0;
                        mState = States.Stance;
                        break;

                    case States.Stance:
                        if(mForward)
                        {
                            mState = States.WalkStart;
                            mVictoria.PlayClip("walkstartbazooka");
                        }
                        if (lLeft || lRight)
                        {
                            mState = States.TurnStart;
                        }
                        if (mAiming)
                            mState = States.StartRaiseBazooka;
                        break;
                    case States.TurnStart:
                        location.Y = 0;
                        if (lLeft)
                        {
                            mVictoria.PlayClip("leftturn");
                            mState = States.LeftTurn;
                        }
                        else if (lRight)
                        {
                            mState = States.RightTurn;
                            mVictoria.PlayClip("rightturn");
                        }
                        else
                            mState = States.Stance;
                        break;

                    case States.LeftTurn:
                        if (mVictoria.FinishedAnimation() && lLeft)
                        {
                            mState = States.TurnStart;
                            lDelta = 0;
                        }
                        else if (!lLeft)
                            mState = States.StanceStart;
                        break;

                    case States.RightTurn:
                        if (mVictoria.FinishedAnimation() && lRight)
                        {
                            mState = States.TurnStart;
                            lDelta = 0;
                        }
                        else if (!lRight)
                            mState = States.StanceStart;
                        break;

                    case States.WalkLoop:

                        if (!mForward)
                        {
                            lDelta = 0;
                            mState = States.StanceStart;
                        }
                        else if (mVictoria.FinishedAnimation())
                        {
                            mState = States.WalkLoopStart;
                            mVictoria.PlayClip("walkloopbazooka");
                        }
                        break;

                    case States.WalkLoopStart:
                        location.Y = 0.0f;
                        
                        lDelta = 0;
                        mState = States.WalkLoop;
                        break;

                    case States.WalkStart:
                        if (!mForward)
                        {
                            mState = States.StanceStart;
                            mVictoria.PlayClip("stance");
                        }
                        else if (mVictoria.FinishedAnimation())
                        {

                            mState = States.WalkLoopStart;
                            mVictoria.PlayClip("walkloopbazooka");
                        }
                        break;
                    case States.StartRaiseBazooka:
                        mVictoria.PlayClip("raisebazooka").Speed = 1;
                        mState = States.RaisingBazooka;

                        break;

                    case States.RaisingBazooka:
                        if (!mAiming)
                        {
                            mVictoria.PlayClip("lowerbazooka");
                            mState = States.LowerBazooka;
                        }
                        if (mVictoria.FinishedAnimation())
                        {
                            mState = States.EndRaiseBazooka;
                        }

                        break;
                    case States.EndRaiseBazooka:

                        if (!mAiming)
                        {
                            mAzimuth = 0;
                            mElevation = 0;
                            mVictoria.PlayClip("lowerbazooka").Speed = 1;
                            
                            mState = States.LowerBazooka;
                        }
                        else
                        {
                            mAzimuth += GetPanRate(lKeyboardState) * (float)lDelta;
                            mElevation += GetPitchRate(lKeyboardState) * (float)lDelta;
                        }
                        if (lKeyboardState.IsKeyDown(Keys.Space) && mLastKeyBoardState.IsKeyUp(Keys.Space))
                            mBazooka.FireBazooka();
                        break;
                    case States.LowerBazooka:
                        if (mVictoria.FinishedAnimation())
                        {
                            mState = States.StanceStart;
                            mVictoria.PlayClip("stance");
                        }
                        break;
                }
                

                // 
                // State update
                //




                mVictoria.Update(lDelta);
                mBazooka.Update(gameTime);

                //
                // Part 1:  Compute a new orientation
                //

                Matrix lDeltaMatrix = mVictoria.DeltaMatrix;
                float lDeltaAngle = (float)Math.Atan2(lDeltaMatrix.Backward.X, lDeltaMatrix.Backward.Z);
                float lNewOrientation = orientation + lDeltaAngle;

                //mAzimuth += lDeltaAngle;

                if (mAzimuth > 1.0)
                    mAzimuth = 1.0f;
                else if (mAzimuth < -1.0)
                    mAzimuth = -1.0f;

                if (mElevation > 0.5f)
                    mElevation = 0.5f;
                else if (mElevation < -0.5f)
                    mElevation = -0.5f;



                //
                // Part 2:  Compute a new location
                //

                // We are likely rotated from the angle the model expects to be in
                // Determine that angle.
                Matrix lRootMatrix = mVictoria.RootMatrix;
                float lActualAngle = (float)Math.Atan2(lRootMatrix.Backward.X, lRootMatrix.Backward.Z);
                Vector3 lNewLocation = location + Vector3.TransformNormal(mVictoria.mDeltaPosition, Matrix.CreateRotationY(lNewOrientation - lActualAngle));

                Vector3 lWallNormal = Vector3.Zero;

                //
                // I'm just taking these here.  You'll likely want to add something 
                // for collision detection instead.
                //
                string region = TestRegion(lNewLocation);//, ref lWallNormal);

                if (region.StartsWith("W"))
                    System.Diagnostics.Trace.WriteLine(region);
                if (LastRegion == "")
                    LastRegion = region;

                bool lCollision = TestForCollision(region);

                if (region != "" && lCollision)
                    location = lNewLocation;
                if (region != LastRegion)
                    location = lNewLocation;

                LastRegion = region;
                orientation = lNewOrientation;
                SetPlayerTransform();
                mVictoria.ComputeAbsoluteTransforms();

               mBazookaMatrix = Matrix.CreateRotationX(MathHelper.ToRadians(109.5f)) *
                        Matrix.CreateRotationY(MathHelper.ToRadians(9.7f)) *
                        Matrix.CreateRotationZ(MathHelper.ToRadians(72.9f)) *
                        Matrix.CreateTranslation(-9.6f, 11.85f, 21.1f) *
                        mVictoria.AbsoTransforms("Bip01 R Hand") *
                        transform;

                mLastKeyBoardState = lKeyboardState;

                lDeltaTotal -= lDelta;
            } while(lDeltaTotal > 0);

            
            /*Matrix lCameraMatrix = Matrix.CreateRotationY(mAzimuth) * transform;
            Vector3 lNewCamLocation = location + new Vector3(-lCameraMatrix.Backward.X * 150, 180 + mElevation * 100, -lCameraMatrix.Backward.Z * 150);

            Vector3 lNormal = Vector3.Zero;

            string lCamRegion = game.CollisionManager.TestRegion(lNewCamLocation, ref lNormal);

            if (lCamRegion == "")
            {
                // The camera is no longer in the bounds of the collision map
                Vector3 lEyeToPlayer = location - game.Camera.DesiredEye;

                if (lEyeToPlayer.Length() <= mCameraDistance)
                {
                    //game.Camera.DesiredEye = game.CollisionManager
                }

            }
            else
                game.Camera.DesiredEye = lNewCamLocation;*/

            //Old Camera stuff
            Vector3 ltry = location + new Vector3(-200, 150, 0);
            Matrix rotate = Matrix.CreateTranslation(-location) * Matrix.CreateRotationY(orientation - (float)Math.PI/2) * Matrix.CreateTranslation(location);
            game.Camera.Eye = Vector3.Transform(ltry, rotate);
            game.Camera.Center = location + new Vector3(0, 75, 0);
        }

        private float GetPanRate(KeyboardState aKeyboardState)
        {
            if (aKeyboardState.IsKeyDown(Keys.A))
                return mPanRate;
            else if (aKeyboardState.IsKeyDown(Keys.D))
                return -mPanRate;
            else
                return 0;
        }

        private float GetPitchRate(KeyboardState aKeyboardState)
        {
            if (aKeyboardState.IsKeyDown(Keys.W))
                return mPitchRate;
            else if (aKeyboardState.IsKeyDown(Keys.S))
                return -mPitchRate;
            else
                return 0;

        }


        /// <summary>
        /// This function is called to draw the player.
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="gameTime"></param>
        public void Draw(GraphicsDeviceManager graphics, GameTime gameTime)
        {
            Matrix lTransform = Matrix.CreateRotationY(orientation);
            lTransform.Translation = location;
            mVictoria.Draw(graphics, gameTime, lTransform);

            // This should rotate and place the bazooka in the hands of victoria
            mBazooka.Draw(graphics, gameTime, mBazookaMatrix);


        }

        private bool TestForCollision(string aRegion)
        {
            bool lTemp = false;

            if (aRegion.StartsWith("R_Door"))
            {
                int lSection = (int)Char.GetNumericValue(aRegion[aRegion.Length - 1]);
                Vector3 lVect1 = location + new Vector3(0, 180, 0);
                Vector3 lVect2 = lVect1 + transform.Backward + new Vector3(0, -0.1f, 0);
                Vector3 lVect3 = lVect2 - lVect1;

                foreach (PrisonModel lModel in game.Models)
                {
                    bool lTemp2 = lModel.CheckForOpenDoors(lSection, lVect3, lVect1);

                    lTemp = lTemp || lTemp2;
                }
            }
            else
                lTemp = true;

            if (aRegion.StartsWith("R_Section"))
            {

                foreach (PrisonModel lModel in game.Models)
                {
                    if (aRegion.EndsWith("1"))
                    {
                        lModel.Slimed = false;
                        if (LastRegion != aRegion)
                            mBazooka.ReloadBazooka();
                    }


                    if (aRegion.EndsWith("6"))
                    {
                        lModel.Slimed = true;

                    }

                    mVictoria.SlimeWeight = lModel.SlimeWeight;
                    mBazooka.SlimeWeight = lModel.SlimeWeight;

                    game.SlimeWeight = lModel.SlimeWeight;
                    lModel.CloseOpenDoors();
                }
            }
            return lTemp;
        }
        /// <summary>
        /// Tests to see if a vector is inside a region of the collision floor plan.
        /// </summary>
        /// <param name="v3"></param>
        /// <returns></returns>
        public string TestRegion(Vector3 v3)//, ref Vector3 aNormal)
        {
            // Convert to a 2D Point
            float x = v3.X;
            float y = v3.Z;

            foreach (KeyValuePair<string, List<Vector2>> region in regions)
            {
                //Code not needed untill we started shooting pies since 
                /*// For now we ignore the walls
                if (region.Key.StartsWith("W"))
                {
                    //System.Diagnostics.Trace.Write(region.Key.ToString());
                    continue;
                }*/

                for (int i = 0; i < region.Value.Count; i += 3)
                {
                    float x1 = region.Value[i].X;
                    float x2 = region.Value[i + 1].X;
                    float x3 = region.Value[i + 2].X;
                    float y1 = region.Value[i].Y;
                    float y2 = region.Value[i + 1].Y;
                    float y3 = region.Value[i + 2].Y;

                    
                    //Computes the edge normal for the region.
                    // The final one will be the edge normal for that which we've intersected.
                    //aNormal.X = y1 - y2;
                    //aNormal.Y = 0;
                    //aNormal.Z = x2 - x1;
                    //aNormal.Normalize();


                    float d = 1.0f / ((x1 - x3) * (y2 - y3) - (x2 - x3) * (y1 - y3));
                    float l1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) * d;
                    if (l1 < 0)
                        continue;

                    float l2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) * d;
                    if (l2 < 0)
                        continue;

                    float l3 = 1 - l1 - l2;
                    if (l3 < 0)
                        continue;

                    return region.Key;
                }
            }

            return "";
        }

        private void ComputeBoundingCylinder()
        {
            mBoundingCyl.Location = location;
        }

    }
}
